/**
 * objectloader.cpp
 * 
 * @author Zsolt Horváth 
 * @date   1. 4. 2011
 * 
 */

#include <iostream>
#include <string.h>
#include <stdlib.h>

#include "objectloader.h"

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif

using namespace std;

ObjectLoader::ObjectLoader (char *filename) {
    // init counters
    this->object.faceCount   = 0;
    this->object.indexCount  = 0;
    this->object.vertexCount = 0;
    this->object.cameraCount = 0;
    this->object.lightCount  = 0;
    
    // try to load our 3DS object file
    this->objectFile = lib3ds_file_open(filename);
    
    // if opening failed print an error message
    if (this->objectFile == NULL) {
        cerr << "Error while loading 3DS file " << filename << endl;
    } else {
        // try to parse an object
        this->parseObject();
    }  
}

ObjectLoader::~ObjectLoader () {
    // release object
    if(this->objectFile != NULL) {
        lib3ds_file_free(this->objectFile);
    }
    
    // free indices
    if (this->object.indices != NULL) {
        free(this->object.indices);
    }
    
    // free vertices
    if (this->object.vertices != NULL) {
        free(this->object.vertices);
    }
    
    // free cameras
    if (this->object.cameraCount > 0) {
        for (unsigned int i=0; i<this->object.cameraCount; i++) {
            free(this->object.cameras[i]);
        }
    }
    
}

void ObjectLoader::parseObject () {
    
    // No nodes?  Fabricate nodes to display all the meshes.
    if (!this->objectFile->nodes) {
        
        Lib3dsNode *node;

        for (int i = 0; i < this->objectFile->nmeshes; ++i) {
            // get current mesh
            Lib3dsMesh *mesh = this->objectFile->meshes[i];
            // create new node
            node = lib3ds_node_new(LIB3DS_NODE_MESH_INSTANCE);
            // copy name of mesh to node
            strcpy(node->name, mesh->name);
            // insert new node
            lib3ds_file_insert_node(this->objectFile, node, NULL);
        }
    }
    
    // evaluate 3DS file
    lib3ds_file_eval(this->objectFile, 0.0f);
    
    // try to init object
    if (initObject()) {
        // initialized successfully, parse each node
        for (Lib3dsNode *node = this->objectFile->nodes; node != 0; node = node->next) {
            this->parseNode(node);
        }
        
        // generate some cameras
        this->generateCameras();
        
        // generate some lights
        this->generateLights();
    }
    
     
}

bool ObjectLoader::initObject () {
    
    this->indexPointer = 0;
    
    bool success = true;
    
    // count faces in each node
    for (Lib3dsNode *node = this->objectFile->nodes; node != 0; node = node->next) {
        this->countFaces(node);
    }
        
    // set up vertex and index count
    // each face contains 3 positions
    this->object.vertexCount = this->object.faceCount * 3;
    // each index contains 3 positions, so we need 3 times less
    // indices than vertices
    this->object.indexCount  = this->object.faceCount;
    
    // allocate memory for indices
    this->object.indices = 
        (VertexIndices *) malloc(sizeof(VertexIndices) * this->object.indexCount);

    // if allocation failed return false
    if (this->object.indices == NULL) {
        success = false;
    } else {
        // allocate memory for vertices
        this->object.vertices = 
            (ObjectVertex *) malloc(sizeof(ObjectVertex) * this->object.vertexCount);
            
        // allocation failed
        if (this->object.vertices == NULL) {
            // free indices
            free(this->object.indices);
            success = false;
        }

    }
    
    // one of allocations failed
    if (!success) {
        // reset counters
        this->object.faceCount   = 0;
        this->object.indexCount  = 0;
        this->object.vertexCount = 0;
        this->indexPointer = 0;
        // set to NULL pointers
        this->object.indices = NULL;
        this->object.vertices = NULL;
    }
    
    lib3ds_file_bounding_box_of_nodes(
        this->objectFile, 1, 0, 0, boundingBoxMin, boundingBoxMax, NULL
    );
    
    // set up sizes and center point    
    this->sizeX = this->boundingBoxMax[0] - this->boundingBoxMin[0];
    this->sizeY = this->boundingBoxMax[1] - this->boundingBoxMin[1];
    this->sizeZ = this->boundingBoxMax[2] - this->boundingBoxMin[2];
    this->sizeMax = MAX(this->sizeX, this->sizeY);
    this->sizeMax = MAX(this->sizeMax, this->sizeZ);
    
    this->center = glm::vec3(
       (this->boundingBoxMin[0] + this->boundingBoxMax[0]) / 2,
       (this->boundingBoxMin[1] + this->boundingBoxMax[1]) / 2,
       (this->boundingBoxMin[2] + this->boundingBoxMax[2]) / 2
    );     
    
    return success;
     
}

void ObjectLoader::countFaces (Lib3dsNode *node) {
   
    // count faces in childs recursively
    for (Lib3dsNode *child = node->childs; child != NULL; child = child->next) {
        this->countFaces(child);
    }
        
    // node is dummy, ignore it
    if (strcmp(node->name, "$$$DUMMY") == 0) {
        return;
    }
    
    // convert node of mesh instance
    Lib3dsMeshInstanceNode *instance = (Lib3dsMeshInstanceNode*) node;
    
    // get index of mesh
    int index = lib3ds_file_mesh_by_name(this->objectFile, instance->instance_name);
    
    // try to index of mesh by node name
    if (index < 0) {
        index = lib3ds_file_mesh_by_name(this->objectFile, node->name);
    }
    
    // no index found, ignore node
    if (index < 0) {
        return;
    }
    
    // get mesh by found index
    Lib3dsMesh *mesh = this->objectFile->meshes[index];
    
    // append node's faces to object faces
    this->object.faceCount += mesh->nfaces;

}

void ObjectLoader::parseNode (Lib3dsNode *node) {

    for (Lib3dsNode *child = node->childs; child != NULL; child = child->next) {
        this->parseNode(child);
    }
    
    // if node has type of mesh instance keep parsing
    if (node->type == LIB3DS_NODE_MESH_INSTANCE) {
        
        // dummy node, just ignore it and return
        if (strcmp(node->name, "$$$DUMMY") == 0) {
            return;
        }        
        
        // convert node to mesh instance
        Lib3dsMeshInstanceNode *instance = (Lib3dsMeshInstanceNode*) node;
        
        // try to get index of mesh by instance name
        int meshIndex = lib3ds_file_mesh_by_name(this->objectFile, instance->instance_name);
        
        // if not found, try to get by node name
        if (meshIndex < 0) {
            meshIndex = lib3ds_file_mesh_by_name(this->objectFile, node->name);
        }
        
        // index not found return
        if (meshIndex < 0) {
            return;
        }
        
        // get current mesh by found index
        Lib3dsMesh *mesh = this->objectFile->meshes[meshIndex];

        if (!mesh->user_id) {
            
            // allocate memory for normals
            float (*normals)[3] = (float(*)[3]) malloc(3 * 3 * sizeof(float) * mesh->nfaces);
            
            // get normals of mesh
            lib3ds_mesh_calculate_vertex_normals(mesh, normals);
            
            // set index pointer
            unsigned int index = this->indexPointer;
            
            // init indices
            unsigned int vertexIndex = 0;
            unsigned short positionIndex = 0;
            unsigned short normalIndex = 0;
            
            // face material
            Lib3dsMaterial *material = NULL;
            VertexColor color;
            
            // parse each face
            for (int i=0; i<mesh->nfaces; i++) {
                
                if (mesh->faces[i].material > 0) {
                    material = this->objectFile->materials[mesh->faces[i].material];
                    
                    for (int i=0; i<3; i++) {
                        color.ambient[i] = material->ambient[i];
                        color.diffuse[i] = material->diffuse[i];
                        color.specular[i] = material->specular[i];
                    }
                    
                    color.ambient[3] = 1.f;
                    color.diffuse[3] = 1.f;
                    color.specular[3] = 1.f;
                } else {
                    material = NULL;
                    
                    for (int i=0; i<3; i++) {
                        color.ambient[i] = 0.5;
                        color.diffuse[i] = 0.5;
                        color.specular[i] = 0.5;
                    }
                    
                    color.ambient[3] = 1.f;
                    color.diffuse[3] = 1.f;
                    color.specular[3] = 1.f;
                }
                
                // and each position and normal
                for (int j=0; j<3; j++) {
                    
                    // get position index from mesh
                    positionIndex = mesh->faces[i].index[j];
                    
                    // calculate normal index of mesh
                    normalIndex = 3*i+j;
                    
                    // calculate vertex index of parsed object
                    vertexIndex = 3*index+j;
                    
                    // get position
                    float *position = mesh->vertices[positionIndex];

                    // and normal
                    float *normal = normals[3*i+j];

                    // add vertex index to the indices
                    this->object.indices[index].index[j] = vertexIndex;

                    // get each coordinates of position and normal
                    for (int k=0; k<3; k++) {
                        this->object.vertices[vertexIndex].position[k] = position[k];
                        this->object.vertices[vertexIndex].normal[k] = normal[k];
                    }
                    
                    this->object.vertices[vertexIndex].color = color;
                    
                }
                
                index++;
            }
            
            // store parsing progress
            this->indexPointer = index;
            
            // free normals of mesh, we do not need them anymore
            free(normals);
        }
        
    }    
}

Camera * ObjectLoader::createCamera () {
    Camera *camera = (Camera *) malloc (sizeof(Camera));
    
    // init structure if allocation was successful
    if (camera != NULL) {
        camera->eye = glm::vec3(0,0,0);
        camera->center = glm::vec3(0,0,0);
        camera->up = glm::vec3(0,1,0);
        
        camera->fovy = 45.0f;
        camera->near = 1.0f;
        camera->far = 1000.0f;
        
        camera->name = "Default name";
    }
    
    return camera;
}

void ObjectLoader::addCamera (Camera *camera) {
    if (this->object.cameraCount < MAX_CAMERA_COUNT) {
        this->object.cameras[this->object.cameraCount++] = camera;
    }
}

Camera * ObjectLoader::getCamera (unsigned int index) {
    if (index < this->object.cameraCount) {
        return this->object.cameras[index];
    } else {
        return NULL;
    }
}

void ObjectLoader::generateCameras () {
        
    // Z-axis camera
    {
        Camera *camera = this->createCamera();
        
        if (camera != NULL) {
               
            camera->center = this->center;
            camera->eye = this->center;
            camera->up = glm::vec3(0,1,0);
            
            camera->eye.z = this->boundingBoxMax[2] + 1.5 * MAX(this->sizeX, this->sizeY);
            camera->near = (camera->eye.z - this->boundingBoxMax[2]) * .5;
            camera->far = (camera->eye.z - this->boundingBoxMin[2]) * 2;
            
            camera->name = "Z-axis camera";
            
            this->addCamera(camera);           
        }
    }
    
    // Y-axis camera
    {
        Camera *camera = this->createCamera();
        
        if (camera != NULL) {
               
            camera->center = this->center;
            camera->eye = this->center;            
            camera->up = glm::vec3(0,0,1);
            
            camera->eye.y = this->boundingBoxMin[1] - 1.5 * MAX(this->sizeX, this->sizeZ);
            camera->near = (this->boundingBoxMin[1] - camera->eye.y) * .5;
            camera->far = (this->boundingBoxMax[1] - camera->eye.y) * 2;
            
            camera->name = "Y-axis camera";            
            this->addCamera(camera);           
        }
    }    

    
    // X-axis camera
    {
        Camera *camera = this->createCamera();
        
        if (camera != NULL) {
               
            camera->center = this->center;
            camera->eye = this->center;
            camera->up = glm::vec3(0,1,0);
            
            camera->eye.x = this->boundingBoxMax[0] + 1.5 * MAX(this->sizeY, this->sizeZ);
            camera->near = (camera->eye.x - this->boundingBoxMax[0]) * .5;
            camera->far = (camera->eye.x - this->boundingBoxMin[0]) * 2;
            
            camera->name = "X-axis camera";
            this->addCamera(camera);           
        }
    }
    
    // ISO camera
    {
        Camera *camera = this->createCamera();
        
        if (camera != NULL) {
               
            camera->center = this->center;
            camera->up = glm::vec3(0,0,1);
            
            camera->eye = glm::vec3(
                boundingBoxMax[0] + .075 * this->sizeMax,
                boundingBoxMin[1] - .075 * this->sizeMax,
                boundingBoxMax[2] + .075 * this->sizeMax
            );
            
            camera->near = (camera->eye.x - this->boundingBoxMax[0]) * .5;
            camera->far = (camera->eye.x - this->boundingBoxMin[0]) * 2;
            
            camera->name = "ISO camera";
            this->addCamera(camera);           
        }
    }       
}

void ObjectLoader::addLight (Light light) {
    if (this->object.lightCount < MAX_LIGHT_COUNT) {
        this->object.lights[this->object.lightCount++] = light;
    }
    
}

Light ObjectLoader::getLight (unsigned int index) {
    if (index < this->object.lightCount) {
        return this->object.lights[index];
    } else {
        return this->createLight();
    }
    
}

void ObjectLoader::generateLights () {
    {
        Light light = this->createLight();  
   
        light.position[0] = this->center.x + this->sizeMax * 0.75;
        light.position[1] = this->center.y - this->sizeMax * 1.0;
        light.position[2] = this->center.z + this->sizeMax * 1.5;
        
        this->addLight(light);
    }

    {
        Light light = this->createLight();  
   
        light.position[0] = this->center.x - this->sizeMax;
        light.position[1] = this->center.y - this->sizeMax;
        light.position[2] = this->center.z + this->sizeMax * 0.75;
        light.position[2] = 0.0;
        
        this->addLight(light);
    }

    {
        Light light = this->createLight();  
   
        light.position[0] = this->center.x;
        light.position[1] = this->center.y + this->sizeMax;
        light.position[2] = this->center.z + this->sizeMax;
        light.position[2] = 0.0;
        
        this->addLight(light);
    }
}

Light ObjectLoader::createLight () {
    Light light;
    
    light.diffuse[0] = 1.0f;
    light.diffuse[1] = 1.0f;
    light.diffuse[2] = 1.0f;
    
    light.ambient[0] = 0.0f;
    light.ambient[1] = 0.0f;
    light.ambient[2] = 0.0f;

    light.specular[0] = 0.0f;
    light.specular[1] = 0.0f;
    light.specular[2] = 0.0f;    
    
    light.position[0] = 0.0f;
    light.position[1] = 0.0f;
    light.position[2] = 0.0f;
    light.position[3] = 1.0f;
    
    return light;
    
}

ObjectVertex * ObjectLoader::getVertices() {
    return this->object.vertices;
}

VertexIndices * ObjectLoader::getIndices() {
    return this->object.indices;
}
        
unsigned int ObjectLoader::getFaceCount () {
    return this->object.faceCount;    
}
      
unsigned int ObjectLoader::getVertexCount () {
    return this->object.vertexCount;    
}

unsigned int ObjectLoader::getIndexCount () {
    return this->object.indexCount;
}

unsigned int ObjectLoader::getCameraCount () {
    return this->object.cameraCount;
}

unsigned int ObjectLoader::getLightCount () {
    return this->object.lightCount;
}